//
// Created by mabinjie on 2024/10/23.
//

#ifndef WRITER_HPP
#define WRITER_HPP

#include <cstdint>
#include <boost/endian/conversion.hpp>

#include "../logger.hpp"
#include "../error.hpp"
#include "../concepts.hpp"

namespace common {

struct Serialized {
    char *data;
    size_t size;

    Serialized() : size(0), data(nullptr) {}
    explicit Serialized(const size_t size) : size(size) {
        data = new char[size];
    }
    Serialized(const char *data, const size_t size) : size(size) {
        this->data = new char[size];
        for (int i = 0; i < size; ++i) {
            this->data[i] = data[i];
        }
    }
    Serialized(const Serialized &other) : size(other.size) {
        data = new char[size];
        for (int i = 0; i < this->size; ++i) {
            this->data[i] = other.data[i];
        }
    }

    ~Serialized() {
        delete[] data;
    }

    [[nodiscard]] std::shared_ptr<Serialized> sub(const size_t begin, size_t len) const {
        if (begin + len >= size)
            len = size - begin - 1;
        return std::make_shared<Serialized>(data+begin, len);
    }

    static std::shared_ptr<Serialized> all_in_one(const std::vector<std::shared_ptr<Serialized>>& all) {
        size_t size = 0;
        for (const auto& serialized : all) {
            size += serialized->size;
        }
        auto one = std::make_shared<Serialized>(size);
        size_t pos = 0;
        for (const auto& serialized : all) {
            memcpy(one->data+pos, serialized->data, serialized->size);
            pos += serialized->size;
        }

        return one;
    }
};

class MessagePackWriter {
    std::shared_ptr<char[]> buffer_;
    std::shared_ptr<char[]> raw_;
    int write_position_ = 0;
    int raw_position_ = 0;
    size_t buffers_size_;

    logger_t logger = Logger::logger("MessagePackWriter");

    std::function<bool(uint8_t, uint16_t&)> escaper_ = [](uint8_t, uint16_t&) {return false;};

public:
    explicit MessagePackWriter(const size_t& size,
        const std::function<bool(uint8_t, uint16_t&)>& escaper = [](uint8_t, uint16_t&) {return false;})
            : buffer_(new char[size], std::default_delete<char[]>()),
              raw_(new char[size], std::default_delete<char[]>()), buffers_size_(size), escaper_(escaper) { }

    MessagePackWriter(const MessagePackWriter& other)
        : MessagePackWriter(other.buffers_size_, other.escaper_) {
        write_position_ = other.write_position_;
        raw_position_ = other.raw_position_;
        this->buffer_ = other.buffer_;
        this->raw_ = other.raw_;
    }

    void writeByte(const uint8_t byte, const bool encode=true) {
        if (uint16_t to_write; encode && escaper_(byte, to_write)) {
            const auto net = boost::endian::native_to_big(to_write);
            memcpy(buffer_.get()+write_position_++, &net, sizeof(to_write));
            write_position_++;
        }
        else
            memcpy(buffer_.get()+write_position_++,&byte, sizeof(byte));
        memcpy(raw_.get()+raw_position_++,&byte, sizeof(byte));
    }

    template<size_t N>
    void writeBytes(uint8_t (&bytes)[N]);

    void writeBytes(const uint8_t *bytes, const size_t len) {
        for (int i = 0; i < len; ++i) {
            const auto byte = bytes[i];
            writeByte(byte);
        }
    }

    template<UnsignedInt uint>
    void writeUint(uint data);

    template<size_t N>
    void writeString(char (&)[N]);

    void writeTime(const time_t time) {
        auto net = boost::endian::native_to_big(time);
        const auto to_encode = reinterpret_cast<unsigned char*>(&net);
        for (int i = 0; i < sizeof(time_t); ++i) {
            writeByte(to_encode[i]);
        }
    }

    template<UnsignedInt uint>
    void writeChecksumAndEndFlag(std::function<uint(const char*, const char*)> checksum_generator, const uint8_t end) {
        auto check_sum = checksum_generator(raw_.get(), raw_.get()+raw_position_);
        writeUint(check_sum);
        writeByte(end, false);
    }

    template<typename T>
    void writePointer(T *data, size_t size) {
        ERR(common::Logger::logger(), "type not support!");
        throw Error(error::WrongFlag, "type not support");
    }
    template<>
    void writePointer<char>(char *data, size_t size);

    template<>
    void writePointer<uint8_t>(uint8_t *data, const size_t size) {
        writeBytes(data, size);
    }

    [[nodiscard]] std::shared_ptr<Serialized> flushAndGet() const {
        auto to_return = std::make_shared<Serialized>(buffer_.get(), write_position_);
        // const auto to_return = std::make_shared<char[]>(write_position_);
        // memcpy(to_return.get(), buffer_, write_position_);
        return to_return;
    }

    [[nodiscard]] size_t size() const {
        return write_position_;
    }

    void combine(const MessagePackWriter& writer) {
        if (const auto combine_size = this->write_position_ + writer.write_position_; combine_size >= buffers_size_) {
            logger->info("The combine_size:{}, which is larger than bufferSize:{}");
            // reallocate memory
            const auto temp = std::make_shared<char[]>(combine_size);
            memcpy(temp.get(), buffer_.get(), write_position_);
            memcpy(temp.get()+write_position_, writer.buffer_.get(), writer.write_position_);
            // delete buffer_;
            buffer_ = temp;
        }
        else {
            memcpy(buffer_.get()+write_position_, writer.buffer_.get(), writer.write_position_);
            memcpy(raw_.get()+raw_position_, writer.raw_.get(), writer.raw_position_);
        }

        write_position_ += writer.write_position_;
        raw_position_ += writer.raw_position_;
    }

    ~MessagePackWriter() = default;
};

template<>
inline void MessagePackWriter::writePointer<char>(char *data, const size_t size) {
    const auto strlen = std::strlen(data);
    for (int i = 0; i < strlen; ++i) {
        if (uint16_t to_write; escaper_(data[i], to_write)) {
            memcpy(buffer_.get()+write_position_++, &to_write, sizeof(to_write));
            write_position_++;
        }
        else
            memcpy(buffer_.get()+write_position_++, data+i, sizeof(char));
        memcpy(raw_.get()+raw_position_++, data+i, sizeof(char));
    }
    const auto fill_size = size - strlen;
    for (int i = 0; i < fill_size; ++i) {
        buffer_[write_position_++] = 0x00;
        raw_[raw_position_++] = 0x00;
    }
}

template<size_t N>
void MessagePackWriter::writeBytes(uint8_t(&bytes)[N]) {
    for (int i = 0; i < N; ++i) {
        const auto byte = bytes[i];
        writeByte(byte);
    }
}

template<UnsignedInt uint>
void MessagePackWriter::writeUint(uint data) {
    auto net = boost::endian::native_to_big(data);
    memcpy(buffer_.get()+write_position_, &net, sizeof(uint));
    memcpy(raw_.get()+raw_position_, &net, sizeof(uint));
    raw_position_ += sizeof(uint);
    write_position_ += sizeof(uint);
}

template<size_t N>
void MessagePackWriter::writeString(char(&str)[N]) {
    auto strlen = std::strlen(str);
    for (int i = 0; i < strlen; ++i) {
        if (uint16_t to_write; escaper_(str[i], to_write)) {
            memcpy(buffer_.get()+write_position_++, &to_write, sizeof(to_write));
            write_position_++;
        }
        else
            memcpy(buffer_.get()+write_position_++, str+i, sizeof(char));
        memcpy(raw_.get()+raw_position_++, str+i, sizeof(char));
    }
    auto fill_size = N - strlen;
    for (int i = 0; i < fill_size; ++i) {
        buffer_[write_position_++] = 0x00;
        raw_[raw_position_++] = 0x00;
    }
}

}

#endif //WRITER_HPP
